from socket import *
import select

# 创建套接字
s = socket(AF_INET, SOCK_STREAM)

# 设置可以重复使用绑定的信息 2倍MLS时间内 只用于服务端先关闭的情况
s.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)

# 绑定本机信息
s.bind(('',8080))

# 变为被动
s.listen(10)

# 创建一个epoll对象
epoll = select.epoll()

# 测试，用来打印套接字对应的文件描述符
# print(s.fileno())
# print(select.EPOLLIN|select.EPOLLET)


# 注册事件到epoll中
# 如果fd已经注册过，则会发生异常 fd为文件描述符
# 将创建的套接字添加到epoll的事件监听中
# EPOLLIN 可读 EPOLLOUT 可写  EPOLLET ET模式
# epoll对文件描述符操作有两种模式 
# LT (level trigger) 没有处理的话会一直通知
# 和 
# ET (edge trigger) 只会通知一次，没有处理的话 就相当于丢失了
epoll.register(s.fileno(), select.EPOLLIN|select.EPOLLET)

connections = {}
addresses = {}

while True:

  # 未指定超时时间则为阻塞等待
  epoll_list = epoll.poll()

  # 对事件进行判断
  for fd, events in epoll_list:

    # 如果是socket创建的套接字被激活
    if fd == s.fileno():
      conn.addr = s.accept()

      print('有新的客户端到来%s'%str(addr))

      # 将 conn 和 addr 信息分别保存起来
      connections[conn.fileno()] = conn
      addresses[conn.fileno()] = addr

      # 向 epoll 中注册连接 socket 的可读事件
      epoll.register(conn.fileno(), select.EPOLLIN|select.EPOLLET)

    elif events == select.EPOLLIN:
      # 从激活 fd 接收 
      # 根据文件描述符找到它对应的套接字 因为文件描述符不能 recv 只有套接字可以，所以需要找到这个 fd 对应的socket
      recvData = connections[fd].recv(1024)

      if len(recvData)>0:
        print('recv:%s'%recvData)
      else:
        # 从 epoll 中移除该 连接 fd
        epoll.unregister(fd)

        #server 侧主动关闭 该 连接 fd
        connections[fd].close()

        print('%s ---offline---'%str(addresses[fd]))
      


